<?php  if ( ! defined('BASEPATH')) exit('No direct script access allowed');

class CI_Table_gateway
{
    private static $instance;
    
    // 子类需要定义的变量值
    protected $database = NULL;
    protected $table    = NULL;
    protected $fields   = NULL;

    // 内部变量
    public    $conn_id               = NULL;
    protected $cache_conns           = NULL;
    protected $db_config             = NULL;
    protected $db_debug              = NULL;
    
    protected $trans_enabled         = TRUE;
    protected $trans_strict          = TRUE;
    protected $_trans_depth          = 0;
    protected $_trans_status         = TRUE; // Used with transactions to determine if a rollback should occur
    
    protected $bind_marker           = '?';
    protected $_reserved_identifiers = array('*'); // Identifiers that should NOT be escaped
    protected $save_queries          = TRUE;
    protected $queries               = array();
    protected $query_times           = array();
    protected $benchmark             = NULL;
    protected $query_count           = NULL;
    protected $affect_rows           = NULL;
    protected $result_id             = NULL;
    protected $result_array          = array();
    protected $num_rows              = NULL;
    protected $current_row           = NULL;

    protected $_like_escape_str     = '';
    protected $_like_escape_chr     = '';
    protected $_escape_char         = '`';
    protected $_protect_identifiers = TRUE;
    protected $_count_string        = "SELECT COUNT(*) AS ";
    
    protected $ar_select            = array();
    protected $ar_set               = array();
    protected $ar_from              = array();
    protected $ar_where             = array();
    protected $ar_orderby           = array();
    protected $ar_keys              = array();
    protected $ar_no_escape         = array();
    protected $ar_limit             = FALSE;
    protected $ar_offset            = FALSE;

    /**
     * Constructor
     */
    public function __construct()
    {
        self::$instance =& $this;

        // 仅仅加载核心类
        $this->load =& load_class('Loader', 'core');

        $this->load->initialize();

        log_message('debug', 'Database Driver Class Initialized');
    }   

    // --------------------------------------------------------------------
    public static function &get_instance()
    {
        return self::$instance;
    }

    public function __destruct()
    {
        $this->close();
    }

    /**
     * 初始化
     *
     * @access  public
     * @param   mixed
     * @return  void
     */
    public function init($config, $debug = TRUE)
    {
        $this->db_debug = $debug === TRUE ? TRUE : FALSE;

        if (trim($this->table) == '')
        {
            if ($this->db_debug)
            {
                return $this->display_error('db_must_set_table');
            }
            return FALSE;
        }
        if ($this->database == '')
        {
            if ($this->db_debug)
            {
                return $this->display_error('db_must_set_database');
            }
            return FALSE;
        }
        $phy_id = $config['db_single'][$this->database];
        if( ! $config['db_physical'][$phy_id])
        {
            if ($this->db_debug)
            {
                return $this->display_error('db_must_set_config');
            }
            return FALSE;
        }
        $this->db_config = $config['db_physical'][$phy_id];

        return TRUE;
    }

    // --------------------------------------------------------------------
    
    /**
     * 建立数据库连接
     * @param   bool  是否使用长连接
     * @access  public
     * @return  resource
     */
    public function connect($mode = 'READ')
    {
       if($this->conn_id)
        return TRUE;
        
       $mode = strtolower(trim($mode)) == 'read' ? 'read' : 'write';

        // if cached , return
        if(is_resource($this->cache_conns[$mode]) OR is_object($this->cache_conns[$mode]))
        {
            $this->conn_id = $this->cache_conns[$mode];
            return TRUE;
        }

        // do connect
        if( ! $this->db_config[$mode])
        {
            if ($this->db_debug)
            {
                $this->display_error('db_must_set_config',$mode);
            }
            return FALSE;
        }
        $db = $this->db_config[$mode];

        $options['PDO::ATTR_ERRMODE'] = PDO::ERRMODE_SILENT;

        // use pconnet ?
        if(TRUE === $db['pconnect'])
        {
            $options['PDO::ATTR_PERSISTENT'] = TRUE;
        }

        $dsn = "{$db['dbdriver']}:host={$db['hostname']};dbname={$this->database}";

        // set db charset
        if( ! empty($db['char_set']))
        {
            //Prior to this version, the charset can't be set in the dsn
            if(is_php('5.3.6'))
            {
                $dsn .= ";charset={$db['char_set']}";
            }
            else
            {
                $set_manually = TRUE;
            }
            //fix for wndows php
            if(PHP_OS == 'WINNT')
            {
            $set_manually = TRUE;
            }
        }

        $this->cache_conns[$mode] = new PDO($dsn, $db['username'], $db['password'], $options);
        if( TRUE === $set_manually)
        {
            $this->cache_conns[$mode]->exec("SET NAMES {$db['char_set']}"); 
        }
        
        if ( ! $this->cache_conns[$mode])
        {
            if ($this->db_debug)
            {
                $this->display_error('db_unable_to_connect');
            }else
            {
                log_message('error', 'Unable to connect to the database');
            }
            return FALSE;
        }

        $this->conn_id = $this->cache_conns[$mode];

        return TRUE;
    }

    // --------------------------------------------------------------------
    
    /**
     * 执行一个SQL语句
     *
     * 查询语句 - 返回(多行/单行)数据
     * 更新语句 - 返回影响行数.
     * 失败 - FALSE
     *
     * @access  public
     * @param   string  SQL语句
     * @param   array   绑定数组,可以为空
     * @param   string  multi 返回多行 / single 返回一行 
     * @return  mixed
     */
    public function query($sql, $binds = FALSE, $return = 'multi')
    {
        if ($sql == '')
        {
            if ($this->db_debug)
            {
                return $this->display_error('db_invalid_query');
            }
            else
            {
                log_message('error', 'Invalid query: '.$sql);
            }
            return FALSE;
        }

        // No connection resource?  Throw an error
        if ( ! $this->conn_id)
        {
            if ($this->db_debug)
            {
                $this->display_error('db_must_set_connect');
            }
            return FALSE;
        }

        // Compile binds if needed
        if ($binds !== FALSE)
        {
            $sql = $this->_compile_binds($sql, $binds);
        }


        // Save the  query for debugging
        if ($this->save_queries == TRUE)
        {
            $this->queries[] = $sql;
        }

        // Start the Query Timer
        $time_start = list($sm, $ss) = explode(' ', microtime());

        // Run the Query
        if (FALSE === $this->_simple_query($sql))
        {
            if ($this->save_queries == TRUE)
            {
                $this->query_times[] = 0;
            }

            // This will trigger a rollback if transactions are being used
            $this->_trans_status = FALSE;

            // grab the error number and message now, as we might run some
            // additional queries before displaying the error
            $error_no = $this->_error_number();
            $error_msg = $this->_error_message();

            if ($this->db_debug)
            {
                // We call this function in order to roll-back queries
                // if transactions are enabled.  If we don't call this here
                // the error message will trigger an exit, causing the
                // transactions to remain in limbo.
                //$this->trans_rollback();

                // If in debug mode, display errors
                return $this->display_error(
                                        array(
                                                '错误代码: '.$error_no,
                                                '错误信息: '.$error_msg,
                                                'SQL语句: '.$sql
                                            )
                                        );
            }else{
                // log error query
                log_message('error', "Code: {$error_no}, Message: {$error_msg}, Query: {$sql}");
            }
            return FALSE;
        }

        // Stop and aggregate the query time results
        $time_end = list($em, $es) = explode(' ', microtime());
        $this->benchmark += ($em + $es) - ($sm + $ss);

        if ($this->save_queries == TRUE)
        {
            $this->query_times[] = ($em + $es) - ($sm + $ss);
        }

        // Increment the query counter
        $this->query_count++;

        // Was the query a "write" type?
        // If so we'll simply return affect rows
        if ($this->is_write_type($sql) === TRUE || stripos($sql ,'SELECT FOUND_ROWS() as total') !== false)
        {
            return $this->affect_rows;
        }

        $result = strtolower($return) === 'single' ? $this->_row_array() : $this->_result_array();
        
        $this->_reset_results();
        return $result;
    }

    // --------------------------------------------------------------------

    /**
     * 获取数据方法
     *
     * 支持 AND 条件,自定义获取列,排序,分页
     *
     * @param   mixed   AND 条件      array('id'=>3,'author'=>'spike') | "id = 3 AND author = 'spike'"
     * @param   mixed   需要获取的列      array('id','author') | "id,author AS a" | 如果为空会自动使用$this->fields的值
     * @param   string  是否需要排序      'id ASC' | 'id,author DESC'
     * @param   int     行数  
     * @param   int     偏移量     
     * @param   bool    是否返回总计  
     * @param   &int    总计数字, 引用传递
     * @return  array   返回多行数据，失败返回FALSE
     */
    public function get($where = NULL, $select = NULL, $orderby = NULL, $limit = NULL, $offset = NULL, $with_total = FALSE, &$total = 0)
    {

        $this->connect('read');

        $this->_select($select);

        if ( ! is_null($where))
        {
            $this->_where($where);
        }

        if ( ! is_null($orderby))
        {
            $this->_order_by($orderby);
        }

        if ( ! is_null($limit))
        {
            $this->_limit($limit, $offset);
        }

        $sql = $this->_compile_select(FALSE , $with_total);

        $this->_reset_select();

        $result = $this->query($sql);

        if($with_total === TRUE)
        {
            $total = $this->total();
        }
        return $result;
    }
    
    public function total()
    {
        return $this->query('SELECT FOUND_ROWS() as total');
    }

    /**
     * 获取单行数据方法
     *
     * 支持 AND 条件,自定义获取列,排序
     *
     * @param   mixed   AND 条件      array('id'=>3,'author'=>'spike') | "id = 3 AND author = 'spike'"
     * @param   mixed   需要获取的列      array('id','author') | "id,author AS a" | 如果为空会自动使用$this->fields的值
     * @param   string  排序              'id ASC' | 'id,author DESC'     
     * @return  array   返回单行数据，失败返回FALSE
     */
    public function get_one($where = NULL, $select = NULL, $orderby = NULL)
    {
        $this->connect('read');

        $this->_select($select);

        if ( ! is_null($where))
        {
            $this->_where($where);
        }

        if ( ! is_null($orderby))
        {
            $this->_order_by($orderby);
        }

        $this->_limit(1);
        
        $sql = $this->_compile_select();

        $this->_reset_select();

        return $this->query($sql, NULL, 'single');
    }

    /**
     * 条件 IN 获取数据方法
     *
     * 支持 条件IN ,自定义获取列,排序
     *
     * @param   array   IN 条件数组     array('id'=> array(1,2,3,4,5))
     * @param   mixed   附加 AND 条件   array('id'=>3,'author'=>'spike') | "id = 3 AND author = 'spike'"
     * @param   mixed   需要获取的列      array('id','author') | "id,author AS a" | 如果为空会自动使用$this->fields的值
     * @param   string  排序              'id ASC' | 'id,author DESC'     
     * @param   int     行数  
     * @param   int     偏移量     
     * @param   bool    是否返回总计  
     * @param   &int    总计数字, 引用传递
     * @return  array   返回多行数据，失败返回FALSE
     */
    public function get_in($where_in = NULL, $where = NULL, $select = NULL, $orderby = NULL, $limit = NULL, $offset = NULL, $with_total = FALSE, &$total = 0)
    {
        $this->connect('read');

        if ( ! is_null($where_in) )
        {
            if( ! is_array($where_in))
            {
                if($this->db_debug)
                {
                    return $this->display_error('key_must_be_an_array');
                }
                return FALSE;
            }

            foreach ($where_in as $k => $v) 
            {
                // 范围为空 返回空数组
                $v = (array)$v;
                if( count($v) === 0)
                {
                    return array();
                }
                $this->_where_in($k, $v);
            }
        }

        if ( ! is_null($where))
        {
            $this->_where($where);
        }

        $this->_select($select);

        if ( ! is_null($orderby))
        {
            $this->_order_by($orderby);
        }

        if ( ! is_null($limit))
        {
            $this->_limit($limit, $offset);
        }

        $sql = $this->_compile_select(FALSE , $with_total);

        $this->_reset_select();

        $result = $this->query($sql);
        
        if($with_total === TRUE)
        {
            $total = $this->total();
        }
        return $result;
    }

    /**
     * 条件 Not_in 获取数据方法
     *
     * 支持 条件Not_in ,自定义获取列,排序
     *
     * @param   array   IN 条件数组         array('id'=>array(1,2,3,4,5))
     * @param   mixed   附加 AND 条件       array('id'=>3,'author'=>'spike') | "id = 3 AND author = 'spike'"
     * @param   mixed   需要获取的列        array('id','author') | "id,author AS a" | 如果为空会自动使用$this->fields的值
     * @param   string  排序                'id ASC' | 'id,author DESC'     
     * @return  array   返回多行数据，失败返回FALSE
     */
    public function get_not_in($where_not_in = NULL, $where = NULL, $select = NULL, $orderby = NULL)
    {
        $this->connect('read');

        if ( ! is_null($where_not_in) )
        {
            if( ! is_array($where_not_in))
            {
                if($this->db_debug)
                {
                    return $this->display_error('key_must_be_an_array');
                }
                return FALSE;
            }

            foreach ($where_not_in as $k => $v) 
            {
                $this->_where_not_in($k, $v);
            }
        }

        if ( ! is_null($where))
        {
            $this->_where($where);
        }

        $this->_select($select);

        if ( ! is_null($orderby))
        {
            $this->_order_by($orderby);
        }
        
        $sql = $this->_compile_select();

        $this->_reset_select();

        return $this->query($sql);
    }

    // --------------------------------------------------------------------
    
    /**
     * 统计行数
     *
     * @param   array   数组          array('id'=>1,'author'=>'Spike','title'=>'How to play Warcraft')
     * @return  mixed   成功返回数字，失败返回FALSE
     */
    public function count($where = NULL)
    {
        $this->connect('read');

        if ($where != NULL)
        {
            $this->_where($where);
        }

        $sql = $this->_compile_select($this->_count_string . $this->_protect_identifiers('numrows'));

        $result = $this->query($sql, NULL, 'single');

        $this->_reset_select();

        return (int) $result['numrows'];
    }

    // --------------------------------------------------------------------

    /**
     * IN 条件统计行数
     * 
     * @param   array   IN 条件数组     array('id'=>array(1,2,3,4,5))
     * @param   array   附加 AND 条件   array('id'=>1,'author'=>'Spike','title'=>'How to play Warcraft')
     * @return  mixed   成功返回数字，失败返回FALSE
     */
    public function count_in($where_in = NULL , $where = NULL)
    {
        $this->connect('read');

        if ( ! is_null($where_in) )
        {
            if( ! is_array($where_in))
            {
                if($this->db_debug)
                {
                    return $this->display_error('key_must_be_an_array');
                }
                return FALSE;
            }

            foreach ($where_in as $k => $v) 
            {
                // 范围为空 0
                $v = (array)$v;
                if( count($v) === 0)
                {
                    return 0;
                }
                $this->_where_in($k, $v);
            }
        }

        if ($where != NULL)
        {
            $this->_where($where);
        }

        $sql = $this->_compile_select($this->_count_string . $this->_protect_identifiers('numrows'));

        $result = $this->query($sql, NULL, 'single');

        $this->_reset_select();

        return (int) $result['numrows'];
    }

    // --------------------------------------------------------------------
    
    /**
     * 删除方法
     *
     * @param   array   数组          array('id'=>1,'author'=>'Spike','title'=>'How to play Warcraft')
     * @return  int     限制删除行数
     * @return  mixed   成功返回影响行数，失败返回FALSE
     */
    public function delete($where = NULL, $limit = NULL)
    {
        $this->connect('write');

        if ($where != NULL)
        {
            $this->_where($where);
        }

        if ($limit != NULL)
        {
            $this->_limit($limit);
        }

        if (count($this->ar_where) == 0)
        {
            if ($this->db_debug)
            {
                return $this->display_error('db_del_must_use_where');
            }

            return FALSE;
        }

        $sql = $this->_delete($this->table, $this->ar_where, array(), $this->ar_limit);

        $this->_reset_write();

        return $this->query($sql);
    }

    /**
     * 批量删除方法
     *
     * @param   array   IN 条件数组           array('id'=>array(1,2,3,4,5))
     * @param   array   附加 AND 条件数组     array('id'=>1,'author'=>'Spike','title'=>'How to play Warcraft')
     * @return  int     限制删除行数
     * @return  mixed   成功返回影响行数，失败返回FALSE
     */
    public function delete_in($where_in = NULL , $where = NULL)
    {
        $this->connect('write');

        if ( ! is_null($where_in) )
        {
            if( ! is_array($where_in))
            {
                if($this->db_debug)
                {
                    return $this->display_error('key_must_be_an_array');
                }
                return FALSE;
            }

            foreach ($where_in as $k => $v) 
            {
                // 范围为空 0
                $v = (array)$v;
                if( count($v) === 0)
                {
                    return 0;
                }
                $this->_where_in($k, $v);
            }
        }
        
        if ($where != NULL)
        {
            $this->_where($where);
        }

        if (count($this->ar_where) == 0)
        {
            if ($this->db_debug)
            {
                return $this->display_error('db_del_must_use_where');
            }

            return FALSE;
        }

        $sql = $this->_delete($this->table, $this->ar_where);

        $this->_reset_write();

        return $this->query($sql);
    }

    // --------------------------------------------------------------------
    
    /**
     * 替换方法
     *
     * @param   array   插入数据        array('id'=>1,'author'=>'Spike','title'=>'How to play Warcraft')
     * @return  mixed   成功返回插入id，失败返回FALSE
     */
    public function replace($set = NULL)
    {   
        $this->connect('write');
        
        $this->_set($set);

        if (count($this->ar_set) == 0)
        {
            if ($this->db_debug)
            {
                return $this->display_error('db_must_use_set');
            }
            return FALSE;
        }

        $sql = $this->_replace($this->_protect_identifiers($this->table, TRUE), array_keys($this->ar_set), array_values($this->ar_set));
        $this->_reset_write();

        return TRUE;
    }

    /**
     * 批量替换方法
     *
     * @param   array   插入数据        array(array('id'=>1,'author'=>'Spike','title'=>'How to play Warcraft'),array('id'=>1,'author'=>'Spike','title'=>'How to play Warcraft II'))
     * @return  mixed   成功返回影响行数，失败返回FALSE
     */
    public function replace_batch($set = NULL)
    {
        $this->connect('write');

        $this->_set_replace_batch($set);

        if (count($this->ar_set) == 0)
        {
            if ($this->db_debug)
            {
                return $this->display_error('db_must_use_set');
            }
            return FALSE;
        }

        if (count($this->ar_keys) == 0)
        {
            if ($this->db_debug)
            {
                return $this->display_error('db_must_use_key');
            }
            return FALSE;
        }

        // Batch this baby
        for ($i = 0, $total = count($this->ar_set); $i < $total; $i = $i + 100)
        {

            $sql = $this->_replace_batch($this->_protect_identifiers($this->table, TRUE), $this->ar_keys, array_slice($this->ar_set, $i, 100));
            $this->query($sql);
        }

        $this->_reset_write();

        return TRUE;
    }

    /**
     * 插入方法
     *
     * @param   array   插入数据        array('id'=>1,'author'=>'Spike','title'=>'How to play Warcraft')
     * @return  mixed   成功返回插入id，失败返回FALSE
     */
    public function insert($set = NULL)
    {   
        $this->connect('write');
        
        $this->_set($set);

        if (count($this->ar_set) == 0)
        {
            if ($this->db_debug)
            {
                return $this->display_error('db_must_use_set');
            }
            return FALSE;
        }

        $sql = $this->_insert($this->_protect_identifiers($this->table, TRUE), array_keys($this->ar_set), array_values($this->ar_set));
        $this->_reset_write();

        $affect_rows = $this->query($sql);
        if( ! $affect_rows)
        {
            return FALSE;
        }
        return $this->conn_id->lastInsertId();
    }

    /**
     * 批量插入方法
     *
     * @param   array   插入数据        array(array('id'=>1,'author'=>'Spike','title'=>'How to play Warcraft'),array('id'=>1,'author'=>'Spike','title'=>'How to play Warcraft II'))
     * @return  mixed   成功返回影响行数，失败返回FALSE
     */
    public function insert_batch($set = NULL)
    {
        $this->connect('write');

        $this->_set_insert_batch($set);

        if (count($this->ar_set) == 0)
        {
            if ($this->db_debug)
            {
                //No valid data array.  Folds in cases where keys and values did not match up
                return $this->display_error('db_must_use_set');
            }
            return FALSE;
        }

        if (count($this->ar_keys) == 0)
        {
            if ($this->db_debug)
            {
                //No valid data array.  Folds in cases where keys and values did not match up
                return $this->display_error('db_must_use_key');
            }
            return FALSE;
        }

        $count = 0;
        // Batch this baby
        for ($i = 0, $total = count($this->ar_set); $i < $total; $i = $i + 100)
        {

            $sql = $this->_insert_batch($this->_protect_identifiers($this->table, TRUE), $this->ar_keys, array_slice($this->ar_set, $i, 100));

            $count += $this->query($sql);
        }

        $this->_reset_write();

        return $count;
    }

    // --------------------------------------------------------------------

    /**
     * 更新方法
     *
     * @param   array   更新的数据       array('id'=>1,'author'=>'Spike','title'=>'How to play Warcraft')
     * @param   mixed   AND 条件      array('id'=>3,'author'=>'spike') | 'id = 3 AND author = 'spike'
     * @param   string  是否需要排序      'id ASC' | 'id,author DESC'
     * @param   int     限制更新行数          
     * @return  mixed   成功返回影响行数，失败返回FALSE
     */
    public function update($set = NULL, $where = NULL, $orderby = NULL, $limit = NULL)
    {
        $this->connect('write');
        
        $this->_set($set);

        if (count($this->ar_set) == 0)
        {
            if ($this->db_debug)
            {
                return $this->display_error('db_must_use_set');
            }
            return FALSE;
        }

        if ($where != NULL)
        {
            $this->_where($where);
        }

        if ($orderby != NULL)
        {
            $this->_order_by($orderby);
        }

        if ($limit != NULL)
        {
            $this->_limit($limit);
        }

        $sql = $this->_update($this->_protect_identifiers($this->table, TRUE), $this->ar_set, $this->ar_where, $this->ar_orderby, $this->ar_limit);

        $this->_reset_write();

        return $this->query($sql);
    }

    /**
     * Update_Batch
     *
     * Compiles an update string and runs the query
     *
     * @param   string  the table to retrieve the results from
     * @param   array   an associative array of update values
     * @param   string  the where key
     * @return  object
     */
    public function update_batch($set = NULL, $index = NULL)
    {
        $this->connect('write');

        if (is_null($index))
        {
            if ($this->db_debug)
            {
                return $this->display_error('db_must_use_index');
            }

            return FALSE;
        }

        if ( ! is_null($set))
        {
            $this->_set_update_batch($set, $index);
        }

        if (count($this->ar_set) == 0)
        {
            if ($this->db_debug)
            {
                return $this->display_error('db_must_use_set');
            }
            return FALSE;
        }

        // Batch this baby
        for ($i = 0, $total = count($this->ar_set); $i < $total; $i = $i + 100)
        {
            $sql = $this->_update_batch($this->_protect_identifiers($this->table, TRUE, NULL, FALSE), array_slice($this->ar_set, $i, 100), $this->_protect_identifiers($index), $this->ar_where);
            $this->query($sql);
        }

        $this->_reset_write();
    }

    
    /**
     * IN 条件更新方法, 支持 IN 条件, AND 条件
     *
     * @param   array   SET 的数组         array('id'=>1,'author'=>'Spike','title'=>'How to play Warcraft')
     * @param   array   附加的 IN 条件     array('id'=>array(1,2,3,4,5))
     * @param   array   附加的 AND 条件          
     * @return  mixed   成功返回影响行数，失败返回FALSE
     */
    public function update_in($set = NULL, $where_in = NULL, $where = NULL)
    {
        $this->connect('write');

        if ( ! is_null($where_in) )
        {
            if( ! is_array($where_in))
            {
                if($this->db_debug)
                {
                    return $this->display_error('key_must_be_an_array');
                }
                return FALSE;
            }

            foreach ($where_in as $k => $v) 
            {
                // 范围为空 0
                $v = (array)$v;
                if( count($v) === 0)
                {
                    return 0;
                }
                $this->_where_in($k, $v);
            }
        }

        $this->_set($set);

        if (count($this->ar_set) == 0)
        {
            if ($this->db_debug)
            {
                return $this->display_error('db_must_use_set');
            }
            return FALSE;
        }

        if ($where != NULL)
        {
            $this->_where($where);
        }

        $sql = $this->_update($this->_protect_identifiers($this->table, TRUE), $this->ar_set, $this->ar_where);

        $this->_reset_write();

        return $this->query($sql);
    }
    
    // --------------------------------------------------------------------
    
    /**
     * 事务开始
     *
     * @access  public
     * @return  bool
     */
    public function trans_begin()
    {
        $this->connect('write');

        if ( ! $this->trans_enabled)
        {
            return TRUE;
        }

        // When transactions are nested we only begin/commit/rollback the outermost ones
        if ($this->_trans_depth > 0)
        {
            return TRUE;
        }
        $this->_trans_depth += 1;
        return $this->conn_id->beginTransaction();
    }

    /**
     * 事务提交
     *
     * @access  public
     * @return  bool
     */
    public function trans_commit()
    {
        if ( ! $this->conn_id)
        {
            return FALSE;
        }

        if ( ! $this->trans_enabled)
        {
            return TRUE;
        }

        // When transactions are nested we only begin/commit/rollback the outermost ones
        if ($this->_trans_depth > 1)
        {
            $this->_trans_depth -= 1;
            return TRUE;
        }

        $this->_trans_depth = 0;
        return $this->conn_id->commit();
    }


    /**
     * 事务回滚
     *
     * @access  public
     * @return  bool
     */
    public function trans_rollback()
    {
        if ( ! $this->conn_id)
        {
            return FALSE;
        }

        if ( ! $this->trans_enabled)
        {
            return TRUE;
        }

        // When transactions are nested we only begin/commit/rollback the outermost ones
        if ($this->_trans_depth > 1)
        {
            $this->_trans_depth -= 1;
            return TRUE;
        }

        $this->_trans_depth = 0;
        return $this->conn_id->rollBack();
    }
    
    public function trans_status()
    {
        return $this->_trans_status;
    }

    // --------------------------------------------------------------------
    
    /**
     * 返回SQL花费时间
     *
     * @access  public
     * @param   int 位数
     * @return  int
     */
    public function waste_time($decimals = 6)
    {
        return number_format($this->benchmark, $decimals);
    }

    // --------------------------------------------------------------------
    
    /**
     * 判断SQL语句类型
     * 查询式 or 更新式
     *
     * @access  public
     * @param   string  An SQL query string
     * @return  boolean
     */
    public function is_write_type($sql)
    {
        if ( ! preg_match('/^\s*"?(SET|INSERT|UPDATE|DELETE|REPLACE|CREATE|DROP|TRUNCATE|LOAD DATA|COPY|ALTER|GRANT|REVOKE|LOCK|UNLOCK)\s+/i', $sql))
        {
            return FALSE;
        }
        return TRUE;
    }

    // --------------------------------------------------------------------

    /**
     * 返回所有已执行的query语句
     *
     * @access  public
     * @return  integer
     */
    public function queries()
    {
        return $this->queries;
    }

    // --------------------------------------------------------------------
    
    /**
     * 关闭数据库连接
     *
     * @access  public
     * @return  void
     */
    public function close()
    {
        $this->conn_id       = NULL;
        $this->cache_conn_id = NULL;
    }

    // --------------------------------------------------------------------

    /**
     * 返回最后一个执行语句
     *
     * @access  public
     * @return  void
     */
    public function last_query()
    {
        return end($this->queries);
    }
    
    // --------------------------------------------------------------------
    
    /**
     * 转义语句
     *
     * @access  public
     * @param   string
     * @return  mixed
     */
    public function escape($str)
    {
        if(is_string($str))
        {
            if( ! $this->_has_null($str))
            {
                $str = "'".$this->_escape_str($str)."'";
            }
        }
        elseif (is_bool($str))
        {
            $str = ($str === FALSE) ? 0 : 1;
        }
        elseif (is_null($str))
        {
            $str = 'NULL';
        }

        return $str;
    }

    // --------------------------------------------------------------------
    
    /**
     * 显示错误信息
     *
     * @access  public
     * @param   string  错误信息
     * @param   string  需要绑定的值
     * @param   boolean 是否需要绑定变量
     * @return  string  application/error_db.php
     */
    protected function display_error($error = '', $swap = '', $native = FALSE)
    {
        $LANG =& load_class('Lang', 'core');
        $LANG->load('db');

        $heading = $LANG->line('db_error_heading');

        if ($native == TRUE)
        {
            $message = (is_array($error)) ? $error : array($error);
        }
        else
        {
            $message = ( ! is_array($error)) ? array(str_replace('%s', $swap, $LANG->line($error))) : $error;
        }

        // Find the most likely culprit of the error by going through
        // the backtrace until the source file is no longer in the
        // database folder.

        $trace = debug_backtrace();

        foreach ($trace as $call)
        {
            if (isset($call['file']) && strpos($call['file'], BASEPATH.'table_gateway') === FALSE)
            {
                // Found it - use a relative path for safety
                $message[] = '文件名：'.str_replace(array(BASEPATH, APPPATH), '', $call['file']);
                $message[] = '行&nbsp;&nbsp;&nbsp;&nbsp;数：'.$call['line'];

                break;
            }
        }

        $error =& load_class('Exceptions', 'core');
        echo $error->show_error($heading, $message, 'error_db');
        exit;
    }

    /**
     * 简单查询
     *
     * @access  public
     * @param   string  the sql query
     * @return  mixed
     */
    protected function _simple_query($sql)
    {
        // No connection resource?  Throw an error
        if ( ! $this->conn_id)
        {
            if ($this->db_debug)
            {
                $this->display_error('db_must_set_connect');
            }
            return FALSE;
        }

        $result_id = $this->conn_id->prepare($sql);
        $this->result_id = $result_id;
        if ($result_id->execute() === FALSE)
        {
            return FALSE;
        }

        if (is_object($result_id))
        {
            if (is_numeric(stripos($sql, 'SELECT FOUND_ROWS() as total')))
            {   
                $result = $result_id->fetchAll();
                $this->affect_rows = $result[0]['total'];
                $result_id->execute();
            }
            else if (is_numeric(stripos($sql, 'SELECT')))
            {            
                $this->affect_rows = count($result_id->fetchAll());
                $result_id->execute();
            }
            else
            {
                $this->affect_rows = $result_id->rowCount();
            }
        }
        else
        {
            $this->affect_rows = 0;
        }
        return $result_id;
    }

    /**
     * 返回多列数据
     *
     * @access  public
     * @return  array
     */
    protected function _result_array()
    {
        // In the event that quernum_rowscaching is on the result_id variable
        // will return FALSE since there isn't a valid SQL resource so
        // we'll simply return an empty array.
        if ($this->result_id === FALSE OR $this->_num_rows() == 0)
        {
            return array();
        }
        
        while ($row = $this->_fetch_assoc())
        {
            $this->result_array[] = $row;
        }

        return $this->result_array;
    }

    /**
     * 返回单列数据
     *
     * @access  public
     * @return  array
     */
    protected function _row_array($n = 0)
    {
        $result = $this->_result_array();

        if (count($result) == 0)
        {
            return $result;
        }

        if ($n !== $this->current_row AND isset($result[$n]))
        {
            $this->current_row = $n;
        }

        return $result[$this->current_row];
    }
    
    /**
     * 返回数据总数
     *
     * @return  int
     */
    protected function _num_rows()
    {
        if (is_int($this->num_rows))
        {
            return $this->num_rows;
        }
        elseif (($this->num_rows = $this->result_id->rowCount()) > 0)
        {
            return $this->num_rows;
        }

        $this->num_rows = count($this->result_id->fetchAll());

        $this->result_id->execute();
        return $this->num_rows;
    }

    /**
     * 报错信息
     *
     * @access  private
     * @return  string
     */
    protected function _error_message()
    {
        $error_array = $this->result_id->errorInfo();
        return $error_array[2];
    }

    /**
     * 错误代码
     *
     * @access  private
     * @return  integer
     */
    protected function _error_number()
    {
        return $this->result_id->errorCode();
    }

    // --------------------------------------------------------------------

    /**
     * Where
     *
     * @param   mixed
     * @param   mixed
     * @param   string
     * @return  object
     */
    protected function _set_where($key, $type = 'AND ', $escape = NULL)
    {
        
        if(is_string($key))
        {
            $key = array($key => NULL);
        }

        // If the escape value was not set will will base it on the global setting
        if ( ! is_bool($escape))
        {
            $escape = $this->_protect_identifiers;
        }

        foreach ($key as $k => $v)
        {
            $prefix = (count($this->ar_where) == 0) ? '' : $type;

            if (is_null($v) && ! $this->_has_operator($k))
            {
                // value appears not to have been set, assign the test to IS NULL
                $k .= ' IS NULL';
            }

            if ( ! is_null($v))
            {
                if ($escape === TRUE)
                {
                    $k = $this->_protect_identifiers($k, TRUE);
                    $this->_val_field($this->_remove_operator($k), TRUE);
                    $v = ' '.$this->escape($v);
                }

                if ( ! $this->_has_null($v) && ! $this->_has_operator($k, FALSE))
                {
                    $k .= ' = ';
                }
            }
            else
            {
                // if it is a custom sql , just let it be
                $k = $this->_protect_identifiers($k);
            }

            $this->ar_where[] = $prefix.$k.$v;
        }
        
        return TRUE;
    }

    /**
     * Where
     *
     *
     * @param   mixed
     * @param   mixed
     * @return  bool
     */
    protected function _where($key, $escape = TRUE)
    {
        if( !is_string($key) AND !is_array($key))
        {
            if($this->db_debug)
            {
                return $this->display_error('key_must_be_string_or_array');
            }
            return FALSE;
        }
        $this->_set_where($key, 'AND ', $escape);
        return TRUE;
    }

    /**
     * Where_in
     *
     * Generates a WHERE field IN ('item', 'item') SQL query joined with
     * AND if appropriate
     *
     * @param   string  The field to search
     * @param   array   The values searched on
     * @return  object
     */
    protected function _where_in($key = NULL, $values = NULL)
    {
        return $this->_set_where_in($key, $values);
    }

    /**
     * Where_not_in
     *
     * Generates a WHERE field NOT IN ('item', 'item') SQL query joined with
     * AND if appropriate
     *
     * @param   string  The field to search
     * @param   array   The values searched on
     * @return  object
     */
    protected function _where_not_in($key = NULL, $values = NULL)
    {
        return $this->_set_where_in($key, $values, TRUE);
    }
    
    /**
     * Where_in
     *
     * Called by where_in, where_in_or, where_not_in, where_not_in_or
     *
     * @param   string  The field to search
     * @param   array   The values searched on
     * @param   boolean If the statement would be IN or NOT IN
     * @param   string
     * @return  object
     */
    protected function _set_where_in($key = NULL, $values = NULL, $not = FALSE, $type = 'AND ')
    {
        if ($key === NULL OR $values === NULL)
        {
            return;
        }

        $this->_val_field($key, TRUE);

        if ( ! is_array($values))
        {
            $values = array($values);
        }

        $not = ($not) ? ' NOT' : '';

        foreach ($values as $value)
        {
            $this->ar_wherein[] = $this->escape($value);
        }

        $prefix = (count($this->ar_where) == 0) ? '' : $type;

        $where_in = $prefix . $this->_protect_identifiers($key) . $not . " IN (" . implode(", ", $this->ar_wherein) . ") ";

        $this->ar_where[] = $where_in;

        // reset the array for multiple calls
        $this->ar_wherein = array();
        return TRUE;
    }

    /**
     * Sets the ORDER BY value
     *
     * @param   string
     * @param   string  direction: asc or desc
     * @return  object
     */
    protected function _order_by($orderby, $direction = '')
    {
        
        if (trim($direction) != '')
        {
            $direction = (in_array(strtoupper(trim($direction)), array('ASC', 'DESC'), TRUE)) ? ' '.$direction : ' ASC';
        }


        if (strpos($orderby, ',') !== FALSE)
        {
            $temp = array();
            foreach (explode(',', $orderby) as $part)
            {
                $part = trim($part);
                $part = $this->_protect_identifiers(trim($part));

                $temp[] = $part;
            }

            $orderby = implode(', ', $temp);
        }
        else
        {
            $orderby = $this->_protect_identifiers($orderby);
        }

        $orderby_statement = $orderby.$direction;

        $this->ar_orderby[] = $orderby_statement;

        return TRUE;
    }

    /**
     * Sets the LIMIT value
     *
     * @param   integer the limit value
     * @param   integer the offset value
     * @return  object
     */
    protected function _limit($value, $offset = '')
    {
        $this->ar_limit = (int) $value;

        if ($offset != '')
        {
            $this->ar_offset = (int) $offset;
        }

        return TRUE;
    }
    
    /**
     * Select
     *
     * @param   string
     * @return  object
     */
    protected function _select($select = NULL, $escape = NULL)
    {
        $val_field = TRUE;

        if($select == FALSE OR (is_string($select) AND trim($select) == '') )
        {
            $select = $this->fields ? $this->fields : array();
            $val_field = FALSE;
        }
        elseif (is_string($select))
        {
            // if is a custom sql ,just let it be
            $select = explode(',', $select);
            $val_field = FALSE;
        }

        foreach ($select as $val)
        {
            $val = trim($val);
            $val = ($escape === FALSE) ? $this->_protect_identifiers($val) : $this->_protect_identifiers($val, TRUE);
            
            if($val_field === TRUE AND ! $this->_val_field($val))
            {
                continue;
            }
            $this->ar_select[] = $val;
            $this->ar_no_escape[] = $escape;
        }
        
        return TRUE;
    }

    /**
     * 测试是否包含SQL符号
     *
     * @access  private
     * @param   string
     * @return  bool
     */
    protected function _has_operator($str)
    {
        $str = trim($str);
        if ( ! preg_match("/(\s|<|>|!|=|is null|is not null|like|not like)/i", $str))
        {
            return FALSE;
        }

        return TRUE;
    }

    /**
     * 测试是否包含特殊片段
     *
     * @access  private
     * @param   string
     * @return  bool
     */
    protected function _has_null($str)
    {
        $str = trim($str);
        return preg_match("/^(is null|is not null)$/i", $str);
    }

    /**
     * 移除SQL符号
     *
     * @access  private
     * @param   string
     * @return  bool
     */
    protected function _remove_operator($str)
    {
        return preg_replace("/(\s|<|>|!|=|is null|is not null|like|not like)/i", '', $str);
    }

    /**
     * 从PDO取出数据
     *
     * @access  private
     * @return  array
     */
    protected function _fetch_assoc()
    {
        return $this->result_id->fetch(PDO::FETCH_ASSOC);
    }

    /**
     * 格式化SQL标识符
     *
     * This function is used extensively by the Active Record class, and by
     * a couple functions in this class.
     * It takes a column or table name (optionally with an alias) and inserts
     * the table prefix onto it.  Some logic is necessary in order to deal with
     * column names that include the path.  Consider a query like this:
     *
     * SELECT * FROM hostname.database.table.column AS c FROM hostname.database.table
     *
     * Or a query with aliasing:
     *
     * SELECT m.member_id, m.member_name FROM members AS m
     *
     * Since the column name can include up to four segments (host, DB, table, column)
     * or also have an alias prefix, we need to do a bit of work to figure this out and
     * insert the table prefix (if it exists) in the proper position, and escape only
     * the correct identifiers.
     *
     * @access  private
     * @param   string
     * @param   bool
     * @return  string
     */
    protected function _protect_identifiers($item, $protect_identifiers = NULL)
    {
        if ( ! is_bool($protect_identifiers))
        {
            $protect_identifiers = $this->_protect_identifiers;
        }

        if (is_array($item))
        {
            $escaped_array = array();

            foreach ($item as $k => $v)
            {
                $escaped_array[$this->_protect_identifiers($k)] = $this->_protect_identifiers($v);
            }

            return $escaped_array;
        }

        // Convert tabs or multiple spaces into single spaces
        $item = preg_replace('/[\t ]+/', ' ', $item);

        // If the item has an alias declaration we remove it and set it aside.
        // Basically we remove everything to the right of the first space
        if (strpos($item, ' ') !== FALSE)
        {
            $alias = strstr($item, ' ');
            $item = substr($item, 0, - strlen($alias));
        }
        else
        {
            $alias = '';
        }

        // This is basically a bug fix for queries that use MAX, MIN, etc.
        // If a parenthesis is found we know that we do not need to
        // escape the data or add a prefix.  There's probably a more graceful
        // way to deal with this, but I'm not thinking of it -- Rick
        if (strpos($item, '(') !== FALSE)
        {
            return $item.$alias;
        }

        // Break the string apart if it contains periods, then insert the table prefix
        // in the correct location, assuming the period doesn't indicate that we're dealing
        // with an alias. While we're at it, we will escape the components
        if (strpos($item, '.') !== FALSE)
        {
            $parts  = explode('.', $item);

            if ($protect_identifiers === TRUE)
            {
                $item = $this->_escape_identifiers($item);
            }

            return $item.$alias;
        }

        if ($protect_identifiers === TRUE AND ! in_array($item, $this->_reserved_identifiers))
        {
            $item = $this->_escape_identifiers($item);
        }

        return $item.$alias;
    }

    /**
     * 转义SQL标识符
     *
     * 转义SQL列名和表名
     *
     * @access  private
     * @param   string
     * @return  string
     */
    protected function _escape_identifiers($item)
    {
        if ($this->_escape_char == '')
        {
            return $item;
        }

        foreach ($this->_reserved_identifiers as $id)
        {
            if (strpos($item, '.'.$id) !== FALSE)
            {
                $str = $this->_escape_char. str_replace('.', $this->_escape_char.'.', $item);

                // remove duplicates if the user already included the escape
                return preg_replace('/['.$this->_escape_char.']+/', $this->_escape_char, $str);
            }
        }

        if (strpos($item, '.') !== FALSE)
        {
            $str = $this->_escape_char.str_replace('.', $this->_escape_char.'.'.$this->_escape_char, $item).$this->_escape_char;
            
        }
        else
        {
            $str = $this->_escape_char.$item.$this->_escape_char;
        }

        // remove duplicates if the user already included the escape
        return preg_replace('/['.$this->_escape_char.']+/', $this->_escape_char, $str);
    }

    /**
     * 重置临时数据
     * 
     * Called by the insert() update() insert_batch() update_batch() and delete() functions
     * 
     * @return  void
     */
    protected function _reset_write()
    {
        $this->ar_set            = array();
        $this->ar_from           = array();
        $this->ar_where          = array();
        $this->ar_orderby        = array();
        $this->ar_keys           = array();
        $this->ar_limit          = FALSE;
    }

    /**
     * Resets the active record values.  Called by the get() function
     *
     * @return  void
     */
    protected function _reset_select()
    {
        $this->ar_select         = array();
        $this->ar_from           = array();
        $this->ar_where          = array();
        $this->ar_orderby        = array();
        $this->ar_no_escape      = array();
        $this->ar_limit          = FALSE;
        $this->ar_offset         = FALSE;
    }
    
    protected function _reset_results()
    {
        $this->result_array = array();
        $this->num_rows = NULL;
        $this->result_id = NULL;
    }

    /**
     * 绑定数据到SQL语句
     *
     * @access  public
     * @param   string  SQL语句
     * @param   array   绑定数组
     * @return  string
     */
    protected function _compile_binds($sql, $binds)
    {
        if (strpos($sql, $this->bind_marker) === FALSE)
        {
            return $sql;
        }

        if ( ! is_array($binds))
        {
            $binds = array($binds);
        }

        // Get the sql segments around the bind markers
        $segments = explode($this->bind_marker, $sql);

        // The count of bind should be 1 less then the count of segments
        // If there are more bind arguments trim it down
        if (count($binds) >= count($segments)) 
        {
            $binds = array_slice($binds, 0, count($segments)-1);
        }

        // Construct the binded query
        $result = $segments[0];
        $i = 0;
        foreach ($binds as $bind)
        {
            $result .= $this->escape($bind);
            $result .= $segments[++$i];
        }
        return $result;
    }

    /**
     * 转义字符串
     *
     * @access  public
     * @param   string
     * @param   bool    是否要在LIKE语句中
     * @return  string
     */
    protected function _escape_str($str, $like = FALSE)
    {
        if (is_array($str))
        {
            foreach ($str as $key => $val)
            {
                $str[$key] = $this->_escape_str($val, $like);
            }

            return $str;
        }

        // No connection resource?  Throw an error
        if ( ! $this->conn_id)
        {
            if ($this->db_debug)
            {
                $this->display_error('db_must_set_connect');
            }
            return FALSE;
        }

        //Escape the string
        $str = $this->conn_id->quote($str);
        
        //If there are duplicated quotes, trim them away
        if (strpos($str, "'") === 0)
        {
            $str = substr($str, 1, -1);
        }
        
        // escape LIKE condition wildcards
        if ($like === TRUE)
        {
            $str = str_replace( array('%', '_', $this->_like_escape_chr),
                                array($this->_like_escape_chr.'%', $this->_like_escape_chr.'_', $this->_like_escape_chr.$this->_like_escape_chr),
                                $str);
        }

        return $str;
    }

    /**
     * The "set_insert_batch" function.  Allows key/value pairs to be set for batch inserts
     *
     * @param   mixed
     * @param   string
     * @param   boolean
     * @return  object
     */
    protected function _set_insert_batch($key, $escape = TRUE)
    {

        if ( ! is_array($key))
        {
            if($this->db_debug)
            {
                return $this->display_error('key_must_be_an_array');
            }
            return FALSE;
        }

        $keys = array_keys(current($key));
        sort($keys);
        foreach ($key as $row)
        {
            if (count(array_diff($keys, array_keys($row))) > 0 OR count(array_diff(array_keys($row), $keys)) > 0)
            {
                $this->ar_set[] = array();
                if($this->db_debug)
                {
                    return $this->display_error('db_batch_error_param');
                }
                return FALSE;
            }

            ksort($row); // puts $row in the same order as our keys

            if ($escape === FALSE)
            {
                $this->ar_set[] =  '('.implode(',', $row).')';
            }
            else
            {
                $clean = array();

                foreach ($row as $value)
                {
                    $clean[] = $this->escape($value);
                }

                $this->ar_set[] =  '('.implode(',', $clean).')';
            }
        }

        foreach ($keys as $k)
        {
            $k = $this->_protect_identifiers($k);
            $this->_val_field($k, TRUE);

            $this->ar_keys[] = $k;
        }

        return TRUE;
    }

    /**
     * The "_set_replace_batch" function.  Allows key/value pairs to be set for batch inserts
     *
     * @param   mixed
     * @param   string
     * @param   boolean
     * @return  object
     */
    protected function _set_replace_batch($key, $escape = TRUE)
    {

        if ( ! is_array($key))
        {
            if($this->db_debug)
            {
                return $this->display_error('key_must_be_an_array');
            }
            return FALSE;
        }

        $keys = array_keys(current($key));
        sort($keys);
        foreach ($key as $row)
        {
            if (count(array_diff($keys, array_keys($row))) > 0 OR count(array_diff(array_keys($row), $keys)) > 0)
            {
                $this->ar_set[] = array();
                if($this->db_debug)
                {
                    return $this->display_error('db_batch_error_param');
                }
                return FALSE;
            }

            ksort($row); // puts $row in the same order as our keys

            if ($escape === FALSE)
            {
                $this->ar_set[] =  '('.implode(',', $row).')';
            }
            else
            {
                $clean = array();

                foreach ($row as $value)
                {
                    $clean[] = $this->escape($value);
                }

                $this->ar_set[] =  '('.implode(',', $clean).')';
            }
        }

        foreach ($keys as $k)
        {
            $k = $this->_protect_identifiers($k);
            $this->_val_field($k, TRUE);

            $this->ar_keys[] = $k;
        }

        return TRUE;
    }

    /**
     * The "set_update_batch" function.  Allows key/value pairs to be set for batch updating
     *
     * @param   array
     * @param   string
     * @param   boolean
     * @return  object
     */
    protected function _set_update_batch($key, $index = '', $escape = TRUE)
    {

        if ( ! is_array($key))
        {
            if($this->db_debug)
            {
                return $this->display_error('key_must_be_an_array');
            }
            return FALSE;
        }

        foreach ($key as $k => $v)
        {
            $index_set = FALSE;
            $clean = array();

            foreach ($v as $k2 => $v2)
            {
                if ($k2 == $index)
                {
                    $index_set = TRUE;
                }
                else
                {
                    $not[] = $k.'-'.$v;
                }

                if ($escape === FALSE)
                {
                    $k = $this->_protect_identifiers($k2);
                    $this->_val_field($k, TRUE);
                    $clean[$k] = $v2;
                }
                else
                {
                    $k = $this->_protect_identifiers($k2, TRUE);
                    $this->_val_field($k, TRUE);
                    $clean[$k] = $this->escape($v2);
                }
            }

            if ($index_set == FALSE)
            {
                if($this->db_debug)
                {
                    return $this->display_error('db_batch_error_index',$index);
                }
                return FALSE;
            }

            $this->ar_set[] = $clean;
        }

        return TRUE;
    }

    /**
     * The "set" function.  Allows key/value pairs to be set for inserting or updating
     *
     * @param   array
     * @param   string
     * @param   boolean
     * @return  object
     */
    protected function _set($key, $escape = TRUE)
    {
        if ( !is_array($key) )
        {
            if($this->db_debug)
            {
                return $this->display_error('key_must_be_an_array');
            }
            return FALSE;
        }
        
        foreach ($key as $k => $v)
        {
            if ($escape === FALSE)
            {
                $k = $this->_protect_identifiers($k);
                if( ! $this->_val_field($k))
                {
                    continue;
                }
                $this->ar_set[$k] = $v;
            }
            else
            {
                $k = $this->_protect_identifiers($k, TRUE);
                if( ! $this->_val_field($k))
                {
                    continue;
                }
                $this->ar_set[$k] = $this->escape($v);
            }

        }

        return TRUE;
    }

    /**
     * [过滤非法列名]
     * @param  string
     * @param  boolean
     * @return mixed
     */
    protected function _val_field($key = NULL, $stop_script = NULL)
    {   

        if( !is_array($this->fields) OR count($this->fields)<1)
        {
            return TRUE;
        }

        if( !in_array(str_replace($this->_escape_char, '', $key), $this->fields))
        {
            if($stop_script === TRUE)
            {
                return $this->display_error(
                    "数据表 {$this->table} 不存在列 {$key}", NULL, TRUE
                );
            }
            else
            {
                return FALSE;
            }
        }
        return TRUE;
    }

    /**
     * Compile the SELECT statement
     *
     * @return  string
     */
    protected function _compile_select($select_override = FALSE, $with_total = FALSE)
    {

        // Write the "select" portion of the query

        if ($select_override !== FALSE)
        {
            $sql = $select_override;
        }
        else
        {
            $sql = 'SELECT ';
            if($with_total === true)
            {
                $sql .= 'SQL_CALC_FOUND_ROWS ';
                            
            }                        
            if (count($this->ar_select) == 0)
            {
                $sql .= '*';
            }
            else
            {
                // Cycle through the "select" portion of the query and prep each column name.
                // The reason we protect identifiers here rather then in the select() function
                // is because until the user calls the from() function we don't know if there are aliases
                foreach ($this->ar_select as $key => $val)
                {
                    $no_escape = isset($this->ar_no_escape[$key]) ? $this->ar_no_escape[$key] : NULL;
                    $this->ar_select[$key] = $this->_protect_identifiers($val, FALSE, $no_escape);
                }

                $sql .= implode(', ', $this->ar_select);
            }
        }

        // ----------------------------------------------------------------

        // Write the "FROM" portion of the query

        if (trim($this->table) !== '')
        {
            $sql .= "\nFROM `{$this->table}`";
        }
        else
        {
            if ($this->db_debug)
            {
                return $this->display_error('db_must_set_table');
            }
            return FALSE;
        }

        // ----------------------------------------------------------------

        // Write the "WHERE" portion of the query

        if (count($this->ar_where) > 0 OR count($this->ar_like) > 0)
        {
            $sql .= "\nWHERE ";
        }

        $sql .= implode("\n", $this->ar_where);

        // ----------------------------------------------------------------

        // Write the "ORDER BY" portion of the query

        if (count($this->ar_orderby) > 0)
        {
            $sql .= "\nORDER BY ";
            $sql .= implode(', ', $this->ar_orderby);
        }

        // ----------------------------------------------------------------

        // Write the "LIMIT" portion of the query

        if (is_numeric($this->ar_limit))
        {
            $sql .= "\n";
            $sql .= "LIMIT ".$this->ar_limit;

            if ($this->ar_offset > 0)
            {
                $sql .= " OFFSET ".$this->ar_offset;
            }
        }

        return $sql;
    }

    // ---------------------------------------------------------------------
    
    /**
     * 更新SQL
     *
     * @access  public
     * @param   string  the table name
     * @param   array   the update data
     * @param   array   the where clause
     * @param   array   the orderby clause
     * @param   array   the limit clause
     * @return  string
     */
    protected function _update($table, $values, $where, $orderby = array(), $limit = FALSE)
    {
        foreach ($values as $key => $val)
        {
            $valstr[] = $key." = ".$val;
        }

        $limit = ( ! $limit) ? '' : ' LIMIT '.$limit;

        $orderby = (count($orderby) >= 1)?' ORDER BY '.implode(", ", $orderby):'';

        $sql = "UPDATE ".$table." SET ".implode(', ', $valstr);

        $sql .= ($where != '' AND count($where) >=1) ? " WHERE ".implode(" ", $where) : '';

        $sql .= $orderby.$limit;

        return $sql;
    }

    /**
     * 批量更新SQL
     *
     * @access  public
     * @param   string  the table name
     * @param   array   the update data
     * @param   array   the where clause
     * @return  string
     */
    protected function _update_batch($table, $values, $index, $where = NULL)
    {
        $ids = array();
        $where = ($where != '' AND count($where) >=1) ? implode(" ", $where).' AND ' : '';

        foreach ($values as $key => $val)
        {
            $ids[] = $val[$index];

            foreach (array_keys($val) as $field)
            {
                if ($field != $index)
                {
                    $final[$field][] =  'WHEN '.$index.' = '.$val[$index].' THEN '.$val[$field];
                }
            }
        }

        $sql = "UPDATE ".$table." SET ";
        $cases = '';

        foreach ($final as $k => $v)
        {
            $cases .= $k.' = CASE '."\n";
            foreach ($v as $row)
            {
                $cases .= $row."\n";
            }

            $cases .= 'ELSE '.$k.' END, ';
        }

        $sql .= substr($cases, 0, -2);

        $sql .= ' WHERE '.$where.$index.' IN ('.implode(',', $ids).')';

        return $sql;
    }

    /**
     * 插入SQL
     *
     * @access  public
     * @param   string  the table name
     * @param   array   the insert keys
     * @param   array   the insert values
     * @return  string
     */
    protected function _insert($table, $keys, $values)
    {
        return "INSERT INTO ".$table." (".implode(', ', $keys).") VALUES (".implode(', ', $values).")";
    }

    /**
     * 替换SQL
     *
     * @access  public
     * @param   string  the table name
     * @param   array   the insert keys
     * @param   array   the insert values
     * @return  string
     */
    protected function _replace($table, $keys, $values)
    {
        return "REPLACE INTO ".$table." (".implode(', ', $keys).") VALUES (".implode(', ', $values).")";
    }

    /**
     * 批量插入SQL
     *
     * @access  public
     * @param   string  the table name
     * @param   array   the insert keys
     * @param   array   the insert values
     * @return  string
     */
    protected function _replace_batch($table, $keys, $values)
    {
        return "REPLACE INTO ".$table." (".implode(', ', $keys).") VALUES ".implode(', ', $values);
    }

    /**
     * 批量插入SQL
     *
     * @access  public
     * @param   string  the table name
     * @param   array   the insert keys
     * @param   array   the insert values
     * @return  string
     */
    protected function _insert_batch($table, $keys, $values)
    {
        return "INSERT INTO ".$table." (".implode(', ', $keys).") VALUES ".implode(', ', $values);
    }

    /**
     * 删除SQL
     * 
     * @access  public
     * @param   string  the table name
     * @param   array   the where clause
     * @param   string  the limit clause
     * @return  string
     */
    protected function _delete($table, $where = array(), $like = array(), $limit = FALSE)
    {
        $conditions = '';

        if (count($where) > 0 OR count($like) > 0)
        {
            $conditions = "\nWHERE ";
            $conditions .= implode("\n", $this->ar_where);

            if (count($where) > 0 && count($like) > 0)
            {
                $conditions .= " AND ";
            }
            $conditions .= implode("\n", $like);
        }

        $limit = ( ! $limit) ? '' : ' LIMIT '.$limit;

        return "DELETE FROM ".$table.$conditions.$limit;
    }

    // --------------------------------------------------------------------

}
